/** 
 * This library is free software; you can redistribute it and/or modify it under 
 * the terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation; either version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this library; if not, write to the Free Software Foundation, 
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 */

package de.stoneageracer.core;

import com.jme3.system.AppSettings;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;

/**
 * This class acts as a container for global variables and constants
 * @author Philipp Jean-Jacques
 */
public abstract class SettingsManager {
    
    private static ArrayList<Setting> settings = new ArrayList<Setting>();
    /**
     * 
     */
    public static final VersionNumber version = new VersionNumber(0.01f, "alpha");
    
    private static boolean init = false;
    
    /**
     * This Method is called at Application startup and can only be called once. It initializes all settings.
     */
    public static void initSettings(){
        if(!init){
            
            // try loading from file
            try {
                loadSettings();
            } catch(Exception ex){
                System.out.println(ex.getMessage());
            }
            
            // set default if loading failed
            if(settings.size() == 0){
                try {
                    
                    addSetting(new Setting("showdebug", 0, 1, 0));
                    addSetting(new Setting("console", 0, 1, 1));
                    addSetting(new Setting("gfx_lightscattering", 0, 1, 1));
                    addSetting(new Setting("gfx_hdr", 0, 1, 0));
                    addSetting(new Setting("gfx_motionblur", 0, 1, 0));
                    addSetting(new Setting("gfx_bloom", 0, 1, 1));
                    addSetting(new Setting("gfx_shadows", 0, 8, 2));
                    addSetting(new Setting("gfx_ssao", 0, 1, 1));
                    addSetting(new Setting("gfx_antialiasing", 0, 4, 2));
                    addSetting(new Setting("resolution_width", 0, 1600, 640));
                    addSetting(new Setting("resolution_height", 0, 1200, 480));
                    addSetting(new Setting("fullscreen", 0, 1, 0));
                    addSetting(new Setting("vsync", 0, 1, 0));
                    
                    // create new config file
                    saveSettings();
                    
                } catch (Exception ex) {
                }
            }
            else System.out.println("Loading succeeded");
        }
        init = true;
    }
    
    /**
     * Saves the settings into the "settings.conf" file
     * @exception IOException is thrown when the file cannot be written to
     */
    public static void saveSettings() throws IOException {
        FileWriter fw = new FileWriter("settings.conf");
        BufferedWriter bw = new BufferedWriter(fw);
        for(int i = 0; i < settings.size(); i++){
            bw.write(settings.get(i).toString() + "\n");
        }
        bw.close();
    }
    
    /**
     * Loads the settings from the "settings.conf" file.
     */
    public static void loadSettings() throws FileNotFoundException, IOException, Exception {
        FileReader fr = new FileReader("settings.conf");
        BufferedReader br = new BufferedReader(fr);
        String line = null;
        while((line = br.readLine()) != null){
            Pattern namepattern = Pattern.compile("\\w+");
            Matcher namematch = namepattern.matcher(line);
            namematch.find();
            Pattern numberpattern = Pattern.compile("\\d+");
            Matcher numbermatch = numberpattern.matcher(line);
            int[] vals = new int[3];
            for(int i = 0; i < vals.length; i++){
                numbermatch.find();
                vals[i] = Integer.parseInt(numbermatch.group());
            }
            addSetting(new Setting(
                    namematch.group(0), 
                    vals[1], 
                    vals[2], 
                    vals[0]
                    ));
        }        
        
    }
    
    /**
     * Add a setting to the settings manager
     * @param setting 
     */
    public static void addSetting(Setting setting){
        settings.add(setting);
    }

    /**
     * Remove a setting from the settings manager
     * @param setting 
     */
    public static void removeSetting(Setting setting){
        settings.remove(setting);
    }
    
    /**
     * Get a list of all available settings
     * @return returns the list of all available settings
     */
    public static String[] getSettingNames(){
        String[] names = new String[settings.size()];
        for(int i = 0; i < names.length; i++){
            names[i] = settings.get(i).getName();
        }
        return names;
    }
    
    /**
     * Change the value of the setting with the given setting name
     * @param settingName The name of the setting
     * @param value The value to be changed
     * @return returns true if the change was successful, false otherwise
     */
    public static boolean setSettingValue(String settingName, int value){
        for(int i = 0; i < settings.size(); i++){
            if(settings.get(i).getName().equals(settingName)){
                try {
                    settings.get(i).changeValue(value);
                } catch (Exception ex) {
                    System.out.println(ex.getMessage());
                    return false;
                }
                return true;
            }
        }
        return false;
    }
    
    public static AppSettings getSettings() throws IOException{
        initSettings();
        
        AppSettings settings = new AppSettings(false);
        
        //Resolution
        settings.setResolution(getValueOfSetting("resolution_width"), getValueOfSetting("resolution_height"));
        
        //Fullscreen
        if(getValueOfSetting("fullscreen")==1)settings.setFullscreen(true);
        else settings.setFullscreen(false);
        
        //VSync
        if(getValueOfSetting("vsync")==1)settings.setVSync(true);
        else settings.setVSync(false);
        
        //Antialiasing
        settings.setSamples((int)Math.pow(2,getValueOfSetting("gfx_antialiasing")));
        
        //Rest
        settings.setIcons(new BufferedImage[]{
            ImageIO.read(new File("logo16.png")),
            ImageIO.read(new File("logo32.png")),
            ImageIO.read(new File("logo128.png"))
        });
        settings.setTitle("StoneAgeRacer "+version);
        settings.setUseInput(true);
        settings.setFrameRate(500);
        settings.setRenderer("LWJGL-OpenGL2");
        
        return settings;
    }
    
    /**
     * Get the value of the setting with the given settin name
     * @param settingName The name of the setting
     * @return returns the value of the setting, returns -1 if no setting with the given name was available
     */
    public static int getValueOfSetting(String settingName){
        for(int i = 0; i < settings.size(); i++){
            if(settings.get(i).getName().equals(settingName)){
                return settings.get(i).getValue();
            }
        }
        return -1;
    }
    
    /**
     * Returns the Version
     * @return String containing Version Info
     */
    public static String toStringStatic(){
        String str = "version "+SettingsManager.version+"\n";
        for(int i = 0; i < settings.size(); i++){
            str += settings.get(i).toString() + "\n";
        }
        return str;
    }
}

/**
 * This class represents a single Setting and its properties.
 * @author Philipp Jean-Jacques
 */
class Setting {
    
    private String name;
    
    int minval;
    int maxval;
    
    int value;
    
    /**
     * Creates a new setting. If minval is 0 and maxval is 1 the value is interpreted as boolean
     * @param name The name of the setting
     * @param minval The minimum value
     * @param maxval The maximum value
     * @param value The actual value 
     */
    public Setting(String name, int minval, int maxval, int value) throws Exception {
        this.name = name;
        this.minval = minval;
        this.maxval = maxval;
        changeValue(value);
    }
    
    /**
     * Changes the value of the setting
     * @param value the new value
     * @throws Exception throws an exception if the value is out of the given setting range
     */
    public void changeValue(int value) throws Exception {
        if(maxval >= value && value >= minval){
            this.value = value;
        }
        else {
            throw new Exception("ERROR: value out of range [" + minval + ", " + maxval + "]");
        }
    }

    /**
     * Method for getting the setting name
     * @return returns the settings name
     */
    public String getName(){
        return name;
    }
    
    /**
     * Method for getting the setting value
     * @return returns the settings value
     */
    public int getValue(){
        return value;
    }

    @Override
    public String toString(){
        return name + " = " + value + "(" + minval + ", " + maxval + ")";
    }
}

/**
 * This class is used to store nice version numbers and compares them
 * @author Philipp Jean-Jacques
 */
class VersionNumber implements Comparable<VersionNumber> {

    /**
     * Suffix for the version number
     */
    String suff;

    /**
     * Decimal version number
     */
    float number;

    /**
     * @param number primary version number
     * @param suff version number 
     */
    public VersionNumber(float number, String suff){
        this.number = number;
        this.suff = suff;
    }

    /**
     * This method is used to compare version numbers
     * @param t The version number that will be compared to
     * @return returns a value that is not 0 if the version number is not the same
     */
    public int compareTo(VersionNumber t) {
        if(t.number == number){
            return t.suff.compareTo(suff);
        }
        else{
            return (int) ((number - t.number)*100000);
        }
    }    
    /**
     * This method is used to parse Strings to VersionNumbers
     * @param str the String to parse
     * @return returns a VersionNumber object
     * @throws de.stoneageracer.core.VersionNumber.VersionNumberFormatException 
     */
    public static VersionNumber parseVersionNumber(String str) throws VersionNumberFormatException {
        return new VersionNumber(Float.parseFloat(str.replaceFirst("\\w+", "")), str.replaceAll("[0-9]+\\.?[0-9]*", ""));
    }

    /**
     * @return returns the version number as a String: primary version number + version suffix (e.g. 0.1a)
     */
    @Override
    public String toString(){
        return number + suff;
    }
    
    
    class VersionNumberFormatException extends Exception {
        
        /**
         * Sets the Exception message
         * @param msg the Exception message
         */
        public VersionNumberFormatException(String msg){
            super(msg);
        }
        
    }

}
